package com.example.jwt.main.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.crypto.MacProvider;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.Key;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 功能概要：JWT的生成和验证
 *
 * @author liaoguangyu
 */
public class JWTUtil {
    /**
     * JWT 生成密钥使用的密码 default 若不设置key 则用此(JWT_ALG, JWT_RULE)生成一个key
     */
    public static final String SECRET = "qazwsx123444$#%#()*&& asdaswwi1235 ?;!@#kmmmpom in***xx**&";

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    /**
     * JWT 添加至HTTP HEAD中的前缀
     */
    public static final String TOKEN_PREFIX = "Bearer";

    /**
     * http头中的字段
     */
    public static final String HEADER_AUTH = "authorization";
    /**
     * jwt payload中的字段:customerId 用户
     */
    public static final String CUSTOMERID = "customer_id";

    /**
     * jwt payload中的字段: appId saas应用超市应用id
     */
    public static final String APP_ID = "app_id";

    /**
     * jwt payload中的字段:tenantId 租户
     */
    public static final String TENANTID = "tenant_id";

    /**
     * jwt payload中的字段:项目
     */
    public static final String PROJECT = "project";

    /**
     * jwt payload中的字段:模块
     */
    public static final String MODULE = "module";

    /**
     * jwt payload中的字段
     */
    public static final String EXPIRATION_DATE = "expiration_date";

    /**
     * token超时时间(毫秒)---暂定一天
     */
    public static final int TOKEN_EXPIRATION_MILLIS_SENCOND = 24 * 60 * 60 * 1000;

    /**
     * OAuth主题
     */
    public static final String OAUTH2 = "OAuth2";

    /**
     * JWT加解密类型
     */
    private static final SignatureAlgorithm JWT_ALG = SignatureAlgorithm.HS256;

    /**
     * 禁止实例化
     */
    private JWTUtil() {
    }

    /**
     * 使用JWT默认方式，生成加解密密钥
     *
     * @param alg 加解密类型
     * @return Key
     */
    public static SecretKey generateKey(SignatureAlgorithm alg) {

        return MacProvider.generateKey(alg);
    }

    /**
     * 使用指定密钥生成规则， 生成JWT加解密密钥
     *
     * @param alg  加解密类型
     * @param rule 密钥生成规则
     * @return Key
     */
    public static SecretKey generateKey(SignatureAlgorithm alg, String rule) {
        //将密钥生成键转换为字节数组
        byte[] bytes = Base64.decodeBase64(rule);
        //根据指定的加密方式，生成密钥
        return new SecretKeySpec(bytes, alg.getJcaName());
    }
    /**
     * 解析token中的信息
     *
     * @param token token
     * @return payload中信息
     * @throws ExpiredJwtException ex
     * @throws Exception ex
     */
    public static Map<String, Object> parseEncryptJWT(String token) throws ExpiredJwtException, Exception {
        Map<String, Object> tokenMap = new HashMap<>();
        if (StringUtils.isEmpty(token)) {
            return tokenMap;
        }
        Jws<String> jwtBody = Jwts.parser()
                .setSigningKey(SECRET)
                .parsePlaintextJws(token.replace(TOKEN_PREFIX, ""));
        String encryptBody = jwtBody.getBody();
        Map<String, Object> tokenBody = decryptBody(encryptBody);

        String customerId = String.valueOf(tokenBody.get(CUSTOMERID));
        String tenantId = (String) tokenBody.get(TENANTID);
        String appId = (String) tokenBody.get(JWTUtil.APP_ID);
        Date expirationTime = new Date((long) tokenBody.get(EXPIRATION_DATE));
        tokenMap.put(CUSTOMERID, customerId);
        tokenMap.put(TENANTID, tenantId);
        tokenMap.put(APP_ID, appId);
        tokenMap.put(EXPIRATION_DATE, expirationTime);
        return tokenMap;
    }

    /**
     * decrypt body
     * @param encryptBody encryptBody
     * @return plain body
     * @throws Exception
     */
    protected static Map<String, Object> decryptBody(String encryptBody) throws Exception {
        try {
            String body = AESUtil.decrypt(encryptBody);
            return readValue(body);
        } catch (Exception ex) {
            throw new Exception("Unable to read JSON value: " + encryptBody, ex);
        }


    }

    /**
     * 构建JWT
     *
     * @param alg       jwt 加密算法
     * @param key       jwt 加密密钥
     * @param sub       jwt 面向的用户
     * @param aud       jwt 接收方
     * @param jti       jwt 唯一身份标识
     * @param iss       jwt 签发者
     * @param nbf       jwt 生效日期时间
     * @param ttlMillis jwt 有效时间，单位：毫秒
     * @return JWT字符串
     */
    public static String buildJWT(SignatureAlgorithm alg, Key key, String sub, String aud, String jti, String iss, Date nbf, long ttlMillis) {
        // jwt签发时间
        long nowMillis = System.currentTimeMillis();
        Date iat = new Date(nowMillis);

        // jwt过期时间，过期时间需要大于签发时间
        Date exp = null;
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            exp = new Date(expMillis);
        }
        // 获取JWT字符串
        String compact = Jwts.builder()
                .signWith(alg, key)
                .setSubject(sub)
                .setAudience(aud)
                .setId(jti)
                .setIssuer(iss)
                .setNotBefore(nbf)
                .setIssuedAt(iat)
                .setExpiration(exp)
                .compact();

        // 在JWT字符串前添加"Bearer"字符串，用于加入"Authorization"请求头
        return TOKEN_PREFIX + compact;
    }

    /**
     * 构建加密jwt
     *
     * @param customerId 用户id
     * @param tenantId   租户id
     * @return 生成的jwt字符串
     */
    public static String buildEncryptJWT(String customerId, String tenantId) throws Exception {

        // 生成token过期时间（默认当前时间+1天）
        Date date = new Date(DateUtil.getDaysBefore(-1));
        SimpleDateFormat sdf = new SimpleDateFormat(DateUtil.FMT_DATE_YYYYMMDD_HHMMSS);
        sdf.format(date);

        // 生成payload
        Map<String, Object> payload = new HashMap<String, Object>(16);
        payload.put(CUSTOMERID, customerId);
        payload.put(TENANTID, tenantId);
        payload.put(EXPIRATION_DATE, date);

        String encryptPayload = encryptBody(payload);

        // 生成jwt
        String jwt = Jwts.builder()
                .setPayload(encryptPayload)
                .signWith(JWT_ALG, SECRET)
                .compact();

        return TOKEN_PREFIX + jwt;
    }

    /**
     * @param payload payload
     * @return encrypt body
     * @throws Exception ex
     */
    protected static String encryptBody(Map<String, Object> payload) throws Exception {
        byte[] bytes = OBJECT_MAPPER.writeValueAsBytes(payload);
        String strBody = new String(bytes);
        return AESUtil.encrypt(strBody);
    }


    /**
     * 构建 JWT
     *
     * @param sub       jwt 面向的用户
     * @param aud       jwt 接收方
     * @param jti       jwt 唯一身份标识
     * @param iss       jwt 签发者
     * @param nbf       jwt 生效日期时间
     * @param ttlMillis jwt 有效时间， 单位：毫秒
     * @return JWT字符串
     */
    public static String buildJWT(String sub, String aud, String jti, String iss, Date nbf, long ttlMillis) {
        return buildJWT(JWT_ALG, generateKey(JWT_ALG, SECRET), sub, aud, jti, iss, nbf, ttlMillis);
    }

    /**
     * 构建JWT
     *
     * @param sub       jwt 面向的用户
     * @param jti       jwt 唯一身份标识， 主要用来作为一次性token，从而回避重放攻击
     * @param ttlMillis jwt 有效时间， 单位：毫秒
     * @return JWT字符串
     */
    public static String buildJWT(String sub, String jti, long ttlMillis) {

        return buildJWT(sub, null, jti, null, null, ttlMillis);
    }


    /**
     * 校验JWT
     *
     * @param token  内容文本
     * @param userId userId
     * @return true or false
     */
    public static boolean checkJWT(String token, String userId) {

        if (StringUtils.isBlank(token) || StringUtils.isBlank(userId)) {
            return false;
        }
        try {
            Map<String, Object> tokenBody = Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                    .getBody();
            String tokenUserId = String.valueOf(tokenBody.get(CUSTOMERID));
            return userId.equals(tokenUserId);
        } catch (ExpiredJwtException e) {
            return false;
        }
    }


    /**
     * read value to map
     *
     * @param val val
     * @return Map<String, Object>
     */
    @SuppressWarnings("unchecked")
    protected static Map<String, Object> readValue(String val) {
        try {
            return OBJECT_MAPPER.readValue(val, Map.class);
        } catch (IOException e) {
            throw new MalformedJwtException("Unable to read JSON value: " + val, e);
        }
    }

    /**
     * 解析JWT
     *
     * @param key       jwt 加密密钥
     * @param claimsJws jwt 内容文本
     * @return {@link Jws}
     */
    public static Jws<Claims> parseJWT(Key key, String claimsJws) {
        // 移除JWT前的"Bearer" 字符串
        claimsJws = StringUtils.substringAfter(claimsJws, TOKEN_PREFIX);
        // 解析JWT字符串
        return Jwts.parser().setSigningKey(key).parseClaimsJws(claimsJws);
    }

    /**
     *  校验JWT
     *
     * @param claimsJws jwt 内容文本
     * @return true or false
     */
//    public static Boolean checkJWT(String claimsJws) {
//
//
//        SecretKey key = generateKey(JWT_ALG, JWT_RULE);
//        // 获取JWT的payload部分
//        return parseJWT(key, claimsJws).getBody() != null;
//    }

    /**
     * 校验JWT
     *
     * @param key       jwt 加密密钥
     * @param claimsJws jwt 内容文本
     * @param sub       jwt 面向的用户
     * @return true or false
     */
    public static Boolean checkJWT(Key key, String claimsJws, String sub) {

        // 获取JWT的payload部分
        Claims claims = parseJWT(key, claimsJws).getBody();
        // 比对JWT中的sub字段
        return claims.getSubject().equals(sub);
    }


}
